package lxlimit

import (
	"encoding/json"
	"fmt"
	"gitee.com/lxgow/lxconv"
	"github.com/astaxie/beego/logs"
	redigo "github.com/garyburd/redigo/redis"
	"hash/fnv"
	"strconv"
	"strings"
)

// api限流
// 创建时间:2023/11/24 11:23
// 创建人:lixu

type LimitConfig struct {
	Redis MapRedisConfig `json:"redis"`
	Func  MFuncConfig    `json:"func"`
}

type MFuncItem struct {
	Name  string `json:"name"`
	Times int    `json:"times"` // 每几秒
	Nums  int    `json:"nums"`  // 限制多少次
}

type MFuncConfig struct {
	FunName    string            `json:"fun_name"`
	Open       bool              `json:"open"`
	Ip         MFuncItem         `json:"ip"`
	Kernel     MFuncItem         `json:"kernel"`
	BodyBy     []byte            `json:"bodyBy"`
	Url        string            `json:"url"`
	Params     map[string]string `json:"params"`
	DelGameIDS []string          `json:"delGameIDS"` // 删除某个游戏
	Member     MFuncItem         `json:"member"`
}

type LimitVar struct {
	RedisConfig MapRedisConfig
	RedisPool   *redigo.Pool
	FuncConfig  MFuncConfig
}

var LXLIMITCL LimitVar

// InitDefault 初始化
func InitDefault(cfg LimitConfig) (err error) {

	err = initRedis(cfg.Redis)
	if err != nil {
		logs.Error("InitDefault", "initRedis error::", err)
		return
	}

	LXLIMITCL.FuncConfig = cfg.Func

	return
}

// CheckLimit 检查是否超 出限制
func CheckLimit(cfg MFuncConfig) (gameid, tp string) {
	var (
		times    = 0
		postdata map[string]interface{}
	)

	postdata = make(map[string]interface{})

	if len(cfg.BodyBy) > 0 {
		_ = json.Unmarshal([]byte(cfg.BodyBy), &postdata)
		if postdata["gameid"] != nil {
			gameid = lxconv.ToString(postdata["gameid"])
		}
	}

	if !LXLIMITCL.FuncConfig.Open || cfg.FunName == "" {
		return
	}

	if len(cfg.DelGameIDS) > 0 {

		if cfg.Url != "" {
			for _, gameid := range cfg.DelGameIDS {
				if strings.Contains(cfg.Url, gameid) {
					tp = "gameid"
					break
				}
			}
			if tp != "" {
				return
			}
		}

		if postdata["gameid"] != nil {
			pgameid := lxconv.ToString(postdata["gameid"])
			for _, gid := range cfg.DelGameIDS {
				if pgameid == gid {
					tp = "gameid"
					break
				}
			}
			if tp != "" {
				return
			}
		}
	}

	if cfg.Ip.Name != "" {
		if cfg.Ip.Times == 0 {
			cfg.Ip.Times = LXLIMITCL.FuncConfig.Ip.Times
		}
		if cfg.Ip.Nums == 0 {
			cfg.Ip.Nums = LXLIMITCL.FuncConfig.Ip.Nums
		}
	}

	if cfg.Kernel.Name != "" {
		if cfg.Kernel.Times == 0 {
			cfg.Kernel.Times = LXLIMITCL.FuncConfig.Kernel.Times
		}
		if cfg.Kernel.Nums == 0 {
			cfg.Kernel.Nums = LXLIMITCL.FuncConfig.Kernel.Nums
		}
	}

	if cfg.Member.Times == 0 {
		cfg.Member.Times = LXLIMITCL.FuncConfig.Member.Times
	}
	if cfg.Member.Nums == 0 {
		cfg.Member.Nums = LXLIMITCL.FuncConfig.Member.Nums
	}

	_rds := LXredis{}

	ipif := (cfg.Ip.Times > 0 && cfg.Ip.Name != "")

	if ipif {
		_rds.Name = "ip::" + cfg.Ip.Name
		_rds.Name += getRedisKey(cfg.FunName, cfg.Ip.Name)
		// ip
		if isx, _ := _rds.IsExist(); isx {
			gstr, _ := _rds.GET()
			if gstr != "" {
				times, _ = strconv.Atoi(gstr)
				if times >= cfg.Ip.Nums {
					tp = "ip"
					return
				}
			}
			_rds.INCR()
			//logs.Error("ip:::11::", lxconv.JsonEncode(cfg.Ip))
		} else {
			//logs.Error("ip:::22::", lxconv.JsonEncode(cfg.Ip))
			_rds.Val = "1"
			_rds.setTime(cfg.Ip.Times)
		}

		ex, _ := _rds.TTL()
		if ex < 0 {
			_rds.EXPIRE(cfg.Ip.Times)
		}

	}

	if ipif && cfg.Kernel.Times > 0 && cfg.Kernel.Name != "" {
		_rds.Name = "kernel::" + cfg.Ip.Name
		_rds.Name += getRedisKey(cfg.FunName, cfg.Ip.Name, cfg.Kernel.Name)
		// ip
		if isx, _ := _rds.IsExist(); isx {
			gstr, _ := _rds.GET()
			if gstr != "" {
				times, _ = strconv.Atoi(gstr)
				if times >= cfg.Kernel.Nums {
					tp = "kernel"
					return
				}
			}
			_rds.INCR()
			//logs.Error("Kernel:::11::", lxconv.JsonEncode(cfg.Kernel))
		} else {
			_rds.Val = "1"
			_rds.setTime(cfg.Kernel.Times)
			//logs.Error("Kernel:::22::", lxconv.JsonEncode(cfg.Kernel))
		}

		ex, _ := _rds.TTL()
		if ex < 0 {
			_rds.EXPIRE(cfg.Kernel.Times)
		}

	}

	var uid string

	if cfg.Member.Name != "" {
		if strings.Contains(cfg.Member.Name, "{gameid}") && postdata["gameid"] != nil {
			gameid = lxconv.InterfaceToStr(postdata["gameid"])
			cfg.Member.Name = strings.Replace(cfg.Member.Name, "{gameid}", gameid, -1)
		}

		if strings.Contains(cfg.Member.Name, "{uid}") && postdata["uid"] != nil {
			uid = lxconv.InterfaceToStr(postdata["uid"])
			cfg.Member.Name = strings.Replace(cfg.Member.Name, "{uid}", uid, -1)
		}
	}

	if cfg.Member.Times > 0 && cfg.Member.Name != "" {
		_rds.Name = "member::" + cfg.Member.Name
		_rds.Name += getRedisKey(cfg.FunName)
		// ip
		if isx, _ := _rds.IsExist(); isx {
			gstr, _ := _rds.GET()
			if gstr != "" {
				times, _ = strconv.Atoi(gstr)
				if times >= cfg.Member.Nums {
					tp = "member"
					return
				}
			}
			_rds.INCR()
			//logs.Error("Member:::11::", lxconv.JsonEncode(cfg.Member))
		} else {
			_rds.Val = "1"
			_rds.setTime(cfg.Member.Times)
			//logs.Error("Member:::22::", lxconv.JsonEncode(cfg.Member))
		}

		ex, _ := _rds.TTL()
		if ex < 0 {
			_rds.EXPIRE(cfg.Member.Times)
		}

	}

	return
}

func hashMod(key string, modulus int) int {
	// 使用FNV哈希算法计算哈希值
	hash := fnv.New32a()
	hash.Write([]byte(key))
	hashValue := hash.Sum32()

	// 对哈希值取模得到结果
	result := int(hashValue % uint32(modulus))
	return result
}

func getRedisKey(ccs ...interface{}) (k string) {

	var pms string

	for _, cc := range ccs {
		pms += lxconv.InterfaceToStr(cc)
	}

	mds := hashMod(pms, 100000)

	k = "::" + lxconv.EncryMD5(pms)

	k += fmt.Sprintf("%d", mds)

	return
}
